'use strict';
/**
 * merge the panes method in here. and Left the panes file as the core module lib
 */
var _ = require('lodash'),
	glob = require('glob'),
	fs = require('fs'),
	path = require('path'),
	jwt = require('express-jwt'),
	IsThere = require("is-there");
// the querystringparser module supports nested properties
var urlParser = require('fast-url-parser');
	urlParser.queryString = require('querystringparser');

var jwtSecret;
var expressSupportedMethods = ['all', 'checkout','connect','copy','delete','get','head','lock','merge','mkactivity','mkcol','move','m-search','notify','options','patch','post','propfind','proppatch','purge','put','report','search','subscribe','trace','unlock','unsubscribe'];
var expressOutputMethods = ['json','jsonp','send' , 'xml'];
/**
 * store the inited controllers then we can call later
 */
var _controllers = {};
/**
 * good old PHP port
 */
var exists = function(arr , val)
{
	var idx = _.findIndex(arr , function(v)
	{
		return v === val;
	});
	return (idx>-1) ? true : false;
};
/**
 * good old PHP port
 */
var replaceAll = function(str , search, replace)
{
    return str.replace(new RegExp(search, 'g'), replace);
}
/**
 * after fetch the list of scripts, we might have scripts that we want to load first
 * use this to re-order the loading sequence
 */
var queueJavascripts = function(scripts , preQueueScripts)
{
    var loadFirstScripts = [],
        loadLaterScripts = [],
        ctn = preQueueScripts.length;

    for (var i=0; i<ctn; ++i) {
        var search = new RegExp(preQueueScripts[i] , 'g');
        var idx = _.findIndex(scripts , function(script)
        {
            return (script.match(search));
        });
        loadFirstScripts.push(scripts[idx]);
    }
    loadLaterScripts = _.difference(scripts , loadFirstScripts);
    return loadFirstScripts.concat(loadLaterScripts);
};
/**
 * Get files by glob patterns, from MEAN.js
 */
var getGlobbedFiles = function(globPatterns, removeRoot)
{
    // For context switching
    var _this = this;
    // URL paths regex
    var urlRegex = new RegExp('^(?:[a-z]+:)?\/\/', 'i');
    // The output array
    var output = [];
    // If glob pattern is array so we use each pattern in a recursive way, otherwise we use glob
    if (_.isArray(globPatterns)) {
        globPatterns.forEach(function(globPattern) {
            output = _.union(output, _this.getGlobbedFiles(globPattern, removeRoot));
        });
    } else if (_.isString(globPatterns)) {
        if (urlRegex.test(globPatterns)) {
            output.push(globPatterns);
        } else {
            glob(globPatterns, {
                sync: true
            }, function(err, files) {
                if (removeRoot) {
                    files = files.map(function(file) {
                        file = path.resolve(file);
                        return file.replace(removeRoot, '');
                    });
                }
                output = _.union(output, files);
            });
        }
    }
    return output;
};
/**
 * We now call this method when init the express app
 * and generate all the routes based off
 * 1. The routing table (modified by user)
 * 2. The auto routing generate from the Controller.action
 * DONE: sometime the same controller get call more than once, then we should only init it once.
 *       so we need a way to capture the Controller first before calling the init
 * 14.8.2015 - change the way how the parameters is pass, we mixin this method into the panes object
 *             and bind panes into this one.
 */
var generateRoutes = function(panes , app , secret)
{
    var routes = getRoutesFromControllers(panes , app);
    // we start generating the map like this
    // app[method](path , requestHandler , controller.action , renderHandler);
    _.forIn(routes , function( opt , route )
    {
        var _method = 'all'; // by default set to all
        if (opt.userGenerated===true) {
            if (isOk(opt , 'listen')) {
                if (expressMethods.exists(expressMethods.expressSupportedMethods , opt.listen)) {
                    _method = opt.listen;
                }
            }
        }
        else {
            _method = opt.listen;
        }
        var middlewares = [
                jwtConfigurator(opt.protect , secret , route) ,
                securityHandler(opt.protect , route), // this is where we handle what to do with the UnauthorizedError
                requestHandler ,
                _controllers[opt.controller].action(opt.action) ,
                renderHandler];

        app.route(route)[_method](middlewares);

    });
    return app; // there is not need to return 
};
/**
 * util method to check config parameter
 */
var isOk = function(o , n)
{
    return (!_.isUndefined(o[n]) && !_.isNull(o[n]) && _.isString(o[n]));
};

/**
 * we first glob up all the controller files, then generate a map
 * then we combine this with the routes.js to form a complete map
 */
var getRoutesFromControllers = function(panes , app)
{
    var searchPath = path.resolve( path.join(__dirname , '../app/server/controllers/**/*.js') );

    getGlobbedFiles( searchPath ).forEach(function(controllerPath)
    {
        var ctrl = require(controllerPath)(panes);
        _controllers[ctrl.controllerName] = ctrl;
    });
    // we need to seperate those that is defined by the user and those that is generate by code , because they will be handle differently
    var userRoutes = require('../app/config/system/routes'),
        autoRoutes = app.get('routeMap');
    _.forIn(userRoutes , function(n , key)
    {
        n.userGenerated = true;
    });
    var routes = _.merge(userRoutes , autoRoutes);
	// do we still need this?
    app.set('routeMap' , routes);
    return routes;
};

/**
 * There is a problem with this approach, because we are using a catch all *
 * so even static files, or things that is wrong path will get pass here.
 * instead we do this up side down. We glob up all the controller files. Pre-map them first
 * then combine the routes.js file. if we couldn't find it in this list, then we issue a 404
 */
var requestHandler = function(req , res , next)
{
    var _url = path.normalize(req.originalUrl),
        _parsed = urlParser.parse(_url , true);
    // always overwrite and start a new one here
    res.locals.panes = {
      	params: {
        	params: req.params, // our may be part after /:controller/:action
        	query: _parsed.query,
        	ext: path.extname(_parsed.pathname),
        	body: req.body
      	}
    };
    next();
};

/**
 * final output handler, use the action.config to change how it should handle it
 */
var renderHandler = function(req , res , next)
{
    var _panes = res.locals.panes,
        method = _panes.respond;
    switch (method)
    {
        case 'render':
            var template = _panes.template;
            if (!_.isString(template)) {
                res.status(404).end(); // see if this pass to next, if not then just a 404 status
            }
            else {
                res.render(template , _panes.data);
            }
        break;
        case 'xml':
            //TODO: check if I need to set a appropriate Content-Type here
            res.send(_panes.data);
        break;
        default:
            // should it be this or res?
            res[method](_panes.data);
    };
};

/**
 * simple method to test if the specify hook exists
 * then we require it. If not we just return false
 * @param (string) relative path to the module
 * @TODO need to change the way how to get the path
 */
var getHook = function(hook)
{
    var base = '../app/server/lib/',
        yes = IsThere( path.resolve( path.join(__dirname , base + hook + '.js') ) );
    if (yes) {
        return require(path);
    }
    return false;
};

//////////////////////////////////////////////
//              JWT HANDLER                 //
//////////////////////////////////////////////

/**
* just a place holder method to call next
*/
var nextHandler = function(req , res , next)
{
	next();
};
/**
* afterall we still need this handler
*/
var securityHandler = function(protect , route)
{
	if (protect) {
		/**
		 * 19.5.2015 - it turns out when you are protecting it. You are anticipating 4 arguments!
		 */
		return function(err , req , res , next) {
			if (err && !req.user) {
				var jwtErrorHandler = getHook('jwt_unauthorized_handler');
				if (jwtErrorHandler===false) {
					return res.status(401).send({error: 'User UnauthorizedError'});
				}
				else {
					return jwtErrorHandler(err , req , res , next);
				}
			}
			next();
		}
	}
	else {
		return nextHandler;
	}
};

/**
* setup how we want to configurate jwt with express-jwt
*/
var jwtConfigurator = function(protect , jwtConfig , route)
{
var localJwtMethod = getHook('jwt_handler');// allow overwriting
if (protect) {
	if (localJwtMethod===false) {
		return jwt({secret: jwtConfig.secret});
	}
	else {
		return localJwtMethod(jwtConfig.secret);
	}
}
return nextHandler;
};


// exporting them back
module.exports = {
	expressSupportedMethods: expressSupportedMethods,
	expressOutputMethods: expressOutputMethods,
	exists: exists,
	replaceAll: replaceAll,
	queueJavascripts: queueJavascripts,
	getGlobbedFiles: getGlobbedFiles,
	generateRoutes: generateRoutes
};

// -- EOF --
